/*
	Copyright (c) 2010 <copyright holders>
	This source code is licensed under The MIT License,
	see the file LICENSE for details.
	*/

#include "Connection.hpp"

#include <boost/bind.hpp>
#include <google/protobuf/io/zero_copy_stream_impl_lite.h>

#include "Net.hpp"

namespace spelprog2 {
namespace net {

void Connection::sendMessage(const spelprog2::Message & msg)
{
    netlog << "Sending message: " + msg.DebugString(); //TEMP just for studying
    google::protobuf::uint32 msgType = spelprog2::MessageTypeRegistry::getInstance().getMsgType(msg);

    if (msgType == spelprog2::MessageTypeRegistry::UNRECOGNIZED_TYPE) {
        netlog << "Tried to send message of unregistered type: " + msg.DebugString();
        return;
    }

    size_t bodyLen = Ostream::VarintSize32(msgType) + msg.ByteSize();
    assert(bodyLen <= MAX_MESSAGE_BODY_LEN);
    size_t bufferLen = 4 + bodyLen;

    OutBufferPtr buffer(new OutBufferType[bufferLen]);
    google::protobuf::io::ArrayOutputStream as(buffer.get(), (int) bufferLen);
    Ostream os(&as);
    os.WriteLittleEndian32(bodyLen);
    os.WriteVarint32(msgType);
    msg.SerializeToCodedStream(&os);

    boost::asio::async_write(sock, boost::asio::buffer(buffer.get(), bufferLen),
        boost::bind(&Connection::handleWrite, shared_from_this(), buffer));
}

spelprog2::MessagePtr Connection::getMessage()
{
    spelprog2::MessagePtr msg = msgQueue.front();
    msgQueue.pop_front();

    return msg;
}

void Connection::start(UserId thePeerId) {
    netlog << "Connection accepted, initiating async receive";
    userId = thePeerId;
    startHeaderRead();
}

void Connection::handleWrite(OutBufferPtr buf)
{
    //automatically deallocates buf, as crazy as it may sound
}

void Connection::startHeaderRead()
{
    boost::asio::async_read(sock, boost::asio::buffer(recvBuffer, 4),
        boost::bind(&Connection::handleHeaderRead, shared_from_this(),
        boost::asio::placeholders::error));
}

void Connection::handleHeaderRead(const boost::system::error_code & err)
{
    if (err) {
        //TODO: deal with errors
        return;
    }

    Istream is(recvBuffer.elems, (int) recvBuffer.max_size);

    //read length of message body
    google::protobuf::uint32 bodyLen;
    is.ReadLittleEndian32(&bodyLen);

    startBodyRead(bodyLen);
}

void Connection::startBodyRead(size_t bodyLen)
{
    if (bodyLen > MAX_MESSAGE_BODY_LEN) {
        //TODO: deal with errors
        return;
    }

    boost::asio::async_read(sock, boost::asio::buffer(recvBuffer, bodyLen),
        boost::bind(&Connection::handleBodyRead, shared_from_this(),
            boost::asio::placeholders::error));
}

void Connection::handleBodyRead(const boost::system::error_code & err)
{
    if (err) {
        //TODO: deal with errors
        return;
    }

    Istream is(recvBuffer.elems, (int) recvBuffer.max_size);

    //read message type and inflate
    google::protobuf::uint32 msgType;
    is.ReadVarint32(&msgType);

    //create message
    spelprog2::MessagePtr msg(spelprog2::MessageTypeRegistry::getInstance().createMsg(msgType));
    if (msg) {
        msg->ParseFromCodedStream(&is);
        netlog << "Received message: " + msg->DebugString(); //TEMP just for studying

        const net::NetMessageInfo info = { net_, userId };
        listener_.postMsg(msg, info);
    }

    //restart read prodedure
    startHeaderRead();
}

}; //namespace net
}; //namespace spelprog2
